/*
 * Copyright (c) 2011 Auzingers
 * Created by Oui-IT
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */
package com.auzingers.docsorganizer.googleapi;

import com.auzingers.docsorganizer.entity.GoogleCollection;
import com.auzingers.docsorganizer.entity.GoogleEntry;
import com.auzingers.docsorganizer.entity.GoogleResult;
import com.auzingers.docsorganizer.googleapi.xml.XmlParser;
import com.auzingers.docsorganizer.service.DocsOrganizerDbService;
import com.auzingers.docsorganizer.util.CollectionUtil;
import com.google.api.client.http.GenericUrl;

import java.io.IOException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * Class as an abstraction layer so that the gui part of the application doesn't have to deal with
 * DocsClient etc. directly.
 */
public class GoogleAPI {

    private DocsClient dc;

    private static final String MAX_RESULT_STRING = "max-results=100";

    private XmlParser xmlParser;

    /**
     * Creates a new instance of GoogleAPI.
     * @param docsClient docsclient to use
     */
    public GoogleAPI(DocsClient docsClient) {
        dc = docsClient;
        xmlParser = new XmlParser();
    }

    /**
     * Returns all subcollections of the given collection (which can be root or an id).
     * @param collectionId id of the collection to look into, i.e. 'root' or
     *     '0B0oZ3CeNrlNMNWYxOTE5NTUtMjM4Ny00MmQ0LTk1ZDgtNmJiY2UyZGM0ZDUy'
     * @return List of collection objects
     * @throws IOException if an io error occurs
     */
    public GoogleResult getCollectionsOfCollection(String collectionId) throws IOException {
        return getEntries("https://docs.google.com/feeds/default/private/full/folder%3A" + collectionId
                + "/contents/-/folder?showroot=true&" + MAX_RESULT_STRING);
    }

    /**
     * Returns all documents of the given collection (which can be root or an id).
     * @param collectionId id of the collection to look into, i.e. 'root' or
     *     '0B0oZ3CeNrlNMNWYxOTE5NTUtMjM4Ny00MmQ0LTk1ZDgtNmJiY2UyZGM0ZDUy'
     * @return List of root document objects
     * @throws IOException if an io error occurs
     */
    public GoogleResult getDocumentsOfCollection(String collectionId) throws IOException {
        final String xml = dc.getString(new GenericUrl("https://docs.google.com/feeds/default/private/full/folder%3A"
                + collectionId + "/contents?showroot=true&showfolders=false&" + MAX_RESULT_STRING));
        final GoogleResult result = xmlParser.parseGoogleEntries(xml);
        Collections.sort(result.getEntries());
        return result;
    }

    /**
     * Returns all shared documents that do not have a parent collection (not even the root collection).
     * @param dbService database service
     * @param email name of the current account
     * @return list of GoogleEntry objects
     * @throws IOException if an io error occurs
     */
    public List<GoogleEntry> getSharedDocumentsWithoutParent(DocsOrganizerDbService dbService, String email)
            throws IOException {

        //get shared documents without parent
        final String encodedEmail = URLEncoder.encode(email);
        final List<GoogleEntry> entries = new ArrayList<GoogleEntry>();
        String nextStartKey = null;
        do {
            final StringBuilder url = new StringBuilder("https://docs.google.com/feeds/default/private/full?q=to%3A"
                    + encodedEmail + "&showroot=true&" + MAX_RESULT_STRING);
            if (nextStartKey != null) {
                url.append("&start-key=" + nextStartKey);
            }
            final String xml = dc.getString(new GenericUrl(url.toString()));
            final GoogleResult result = xmlParser.parseGoogleEntries(xml);
            entries.addAll(filterEntriesWithoutParent(result.getEntries()));
            nextStartKey = result.getNextStartKey();
        } while (nextStartKey != null);

        //add (shared) collections without parent
        final List<GoogleCollection> collections = getAllCollections(dbService, false);
        final List<GoogleEntry> filteredCollections = filterEntriesWithoutParent(collections);
        entries.addAll(filteredCollections);

        Collections.sort(entries);
        return entries;
    }

    /**
     * Filters the given list - only keeps those entries that do not have a parent.
     * @param entries entries to filter
     * @return filtered entries
     */
    private List<GoogleEntry> filterEntriesWithoutParent(List<? extends GoogleEntry> entries) {
        final List<GoogleEntry> filteredEntries = new ArrayList<GoogleEntry>();
        for (final GoogleEntry entry : entries) {
            if (entry.getParentRefs().isEmpty()) {
                filteredEntries.add(entry);
            }
        }
        return filteredEntries;
    }

    /**
     * Returns all elements of the given collection - both collections and documents.
     * @param collectionId parent collection id
     * @param nextStartKey start key for query, can be null (when starting from the beginning)
     * @return list of GoogleEntry elements
     * @throws IOException if an io error occurs
     */
    public GoogleResult getElementsOfCollection(String collectionId, final String nextStartKey) throws IOException {
        String url = "https://docs.google.com/feeds/default/private/full/folder%3A" + collectionId
                + "/contents?showroot=true&showfolders=true&" + MAX_RESULT_STRING;
        if (nextStartKey != null) {
            url += "&start-key=" + nextStartKey;
        }
        final String xml = dc.getString(new GenericUrl(url));
        final GoogleResult result = xmlParser.parseGoogleEntries(xml);
        Collections.sort(result.getEntries());
        return result;
    }

    /**
     * Returns all collections of the current Google account.
     * @param dbService service that can be used to access the database
     * @param useCache true if it is ok to use the cache, false if fresh data is necessary
     * @return List of all collections
     * @throws IOException if an io error occurs
     */
    public List<GoogleCollection> getAllCollections(DocsOrganizerDbService dbService, boolean useCache)
            throws IOException {

        final List<GoogleCollection> result;
        List<GoogleCollection> dbCollections = null;
        if (useCache) {
            dbCollections = dbService.getAllCollections();
        }
        if (!useCache || dbCollections == null || dbCollections.isEmpty()) {
            result = new ArrayList<GoogleCollection>();
            GoogleResult googleResult = null;
            do {
                //paging
                String url = "https://docs.google.com//feeds/default/private/full/-/folder"
                        + "?showroot=true&max-results=1000";
                if (googleResult != null && googleResult.getNextStartKey() != null) {
                    url += "&start-key=" + googleResult.getNextStartKey();
                }
                googleResult = getEntries(url);
                final List<GoogleEntry> entries = googleResult.getEntries();
                final List<GoogleCollection> collections = CollectionUtil.convertList(entries, GoogleCollection.class);
                result.addAll(collections);
            } while (googleResult.getNextStartKey() != null);
            dbService.updateCollectionList(result);
        } else {
            result = dbCollections;
        }
        Collections.sort(result);
        return result;
    }

    /**
     * Retrieves all entries from the given url and converts them into a list of GoogleEntry objects.
     * @param url url to query from
     * @return resulting list of GoogleCollection objects
     * @throws IOException if an io error occurs
     */
    private GoogleResult getEntries(String url) throws IOException {
        final String xml = dc.getString(new GenericUrl(url));
        final GoogleResult result = xmlParser.parseGoogleEntries(xml);
        Collections.sort(result.getEntries());
        return result;
    }

    /**
     * Deletes the given google document entry.
     *
     * @param dbService service that can be used to access the database
     * @param entry element to delete
     * @throws IOException if an io error occurs
     */
    public void deleteEntry(DocsOrganizerDbService dbService, GoogleEntry entry) throws IOException {
        final String resourceId = entry.getSimpleResourceId();
        dc.deleteEntry(resourceId);
        dbService.deleteCollection("folder:" + resourceId);
    }

    /**
     * Returns a list of all documents (in any collection) of the current Google account.
     * @return list of document objects
     * @throws IOException if an io error occurs
     */
    public GoogleResult getAllDocuments() throws IOException {
        final String xml = dc.getString(new GenericUrl(
                "https://docs.google.com/feeds/default/private/full?showroot=true"));
        final GoogleResult result = xmlParser.parseGoogleEntries(xml);
        Collections.sort(result.getEntries());
        return result;
    }

    /**
     * Copies the given document to a document with 'newName'. The newly created document is stored in the root
     * collection, NOT in the same collection as the original document.
     * @param documentId id of the document to copy
     * @param newName name for the copy to be created
     * @param targetCollectionId id of the collection where to place the target document into
     * @throws IOException if an io error occurs
     */
    public void copyDocument(String documentId, String newName, String targetCollectionId) throws IOException {
        final String newDocumentId = dc.copyDocument(documentId, newName);
        if (newDocumentId != null && targetCollectionId != null && !"root".equals(targetCollectionId)) {
            moveDocument(newDocumentId, "root", targetCollectionId);
        }
    }

    /**
     * Moves the given document from the old collection into the new collection. This is a two-step process:
     * First the document is added to the new collection, that it is removed from the old collection.
     * @param documentId id of the document to move
     * @param oldCollectionId old parent collection
     * @param newCollectionId id of the new collection
     * @throws IOException if an io error occurs
     */
    public void moveDocument(String documentId, String oldCollectionId, String newCollectionId) throws IOException {
        if (newCollectionId != null) {
            dc.addDocumentToCollection(documentId, newCollectionId);
        }
        dc.removeDocumentFromCollection(documentId, "folder%3A" + oldCollectionId);
    }
}
